"""
推荐系统模块
"""
from django.db.models import Avg, Count, Q, Prefetch
from django.db.models import Avg as DjangoAvg
from django.core.cache import cache
from django.conf import settings
from collections import defaultdict
from apps.attractions.models import Attraction, AttractionReview, AttractionFavorite
from apps.routes.models import RoutePlan
from apps.users.models import User
import hashlib
import json


class RecommendationEngine:
    """推荐引擎"""
    
    def __init__(self):
        pass
    
    def recommend_attractions(self, user_id=None, limit=10, categories=None, use_collaborative=True):
        """
        推荐景点（混合推荐算法）
        :param user_id: 用户ID（可选）
        :param limit: 推荐数量
        :param categories: 分类筛选
        :param use_collaborative: 是否使用协同过滤
        """
        queryset = Attraction.objects.all()
        
        # 分类筛选
        if categories:
            queryset = queryset.filter(category__in=categories)
        
        # 如果有用户ID，使用混合推荐
        if user_id and use_collaborative:
            try:
                user = User.objects.get(id=user_id)
                
                # 1. 基于内容的推荐（用户偏好和历史）
                content_based_scores = self._content_based_recommendation(user, queryset)
                
                # 2. 协同过滤推荐
                collaborative_scores = self._collaborative_filtering_recommendation(user_id, queryset)
                
                # 3. 混合推荐（加权平均）
                hybrid_scores = {}
                for attraction_id in set(list(content_based_scores.keys()) + list(collaborative_scores.keys())):
                    content_score = content_based_scores.get(attraction_id, 0)
                    collab_score = collaborative_scores.get(attraction_id, 0)
                    # 内容推荐权重60%，协同过滤权重40%
                    hybrid_scores[attraction_id] = content_score * 0.6 + collab_score * 0.4
                
                # 按混合分数排序
                sorted_attraction_ids = sorted(hybrid_scores.items(), key=lambda x: x[1], reverse=True)
                attraction_ids = [aid for aid, _ in sorted_attraction_ids[:limit * 2]]  # 多取一些用于筛选
                
                if attraction_ids:
                    queryset = queryset.filter(id__in=attraction_ids)
                    # 保持排序
                    from django.db.models import Case, When
                    preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(attraction_ids)])
                    queryset = queryset.order_by(preserved)
                else:
                    # 如果协同过滤没有结果，回退到基于内容的推荐
                    queryset = self._apply_content_based_filter(user, queryset)
            except User.DoesNotExist:
                pass
        elif user_id:
            # 只使用基于内容的推荐
            try:
                user = User.objects.get(id=user_id)
                queryset = self._apply_content_based_filter(user, queryset)
            except User.DoesNotExist:
                pass
        
        # 按热度和评分排序（如果没有用户特定排序）
        if not (user_id and use_collaborative):
            queryset = queryset.annotate(
                avg_rating=DjangoAvg('reviews__rating')
            ).order_by('-popularity', '-avg_rating', '-rating')
        
        attractions = queryset[:limit]
        
        recommendations = []
        for attraction in attractions:
            score = self._calculate_attraction_score(attraction)
            reason = self._get_recommendation_reason(attraction, user_id)
            
            recommendations.append({
                'attraction': {
                    'id': attraction.id,
                    'name': attraction.name,
                    'description': attraction.description,
                    'category': attraction.get_category_display(),
                    'latitude': float(attraction.latitude),
                    'longitude': float(attraction.longitude),
                    'ticket_price': float(attraction.ticket_price),
                    'popularity': attraction.popularity,
                    'rating': float(attraction.rating),
                    'address': attraction.address,
                },
                'score': score,
                'reason': reason
            })
        
        return recommendations
    
    def _apply_content_based_filter(self, user, queryset):
        """应用基于内容的过滤"""
        preferences = user.preferences or {}
        
        # 基于用户偏好分类
        preferred_categories = preferences.get('categories', [])
        if preferred_categories:
            queryset = queryset.filter(category__in=preferred_categories)
        
        # 基于用户历史路线推荐
        user_routes = RoutePlan.objects.filter(user_id=user.id)
        visited_attraction_ids = set()
        for route in user_routes:
            if route.optimal_route and 'route' in route.optimal_route:
                visited_attraction_ids.update(route.optimal_route['route'])
        
        # 排除已访问的景点
        if visited_attraction_ids:
            queryset = queryset.exclude(id__in=visited_attraction_ids)
        
        return queryset
    
    def _content_based_recommendation(self, user, queryset):
        """基于内容的推荐（优化版：使用缓存和批量查询）"""
        # 构建缓存键
        query_hash = str(hash(str(queryset.query)))[:10]
        cache_key = f'content_based_rec_{user.id}_{query_hash}'
        cached_scores = cache.get(cache_key)
        if cached_scores:
            return cached_scores
        
        scores = {}
        preferences = user.preferences or {}
        
        # 批量获取用户已访问/收藏的景点（优化查询）
        user_favorites = set(AttractionFavorite.objects.filter(user=user).values_list('attraction_id', flat=True))
        user_routes = RoutePlan.objects.filter(user_id=user.id).only('optimal_route')
        visited_attraction_ids = set()
        for route in user_routes:
            if route.optimal_route and 'route' in route.optimal_route:
                visited_attraction_ids.update(route.optimal_route['route'])
        
        # 使用select_related和prefetch_related优化查询
        attractions = queryset.select_related().only(
            'id', 'category', 'popularity', 'rating', 'ticket_price'
        )
        
        # 批量计算分数
        for attraction in attractions:
            score = 0.0
            
            # 如果已访问，跳过
            if attraction.id in visited_attraction_ids or attraction.id in user_favorites:
                continue
            
            # 分类匹配（用户偏好）
            preferred_categories = preferences.get('categories', [])
            if preferred_categories and attraction.category in preferred_categories:
                score += 0.3
            
            # 热度分数
            score += (attraction.popularity / 100) * 0.2
            
            # 评分分数
            score += (attraction.rating / 5) * 0.2
            
            # 价格匹配（用户预算）
            budget = preferences.get('budget')
            if budget and attraction.ticket_price <= budget:
                score += 0.3
            
            scores[attraction.id] = score
        
        # 缓存结果（30分钟）
        cache.set(cache_key, scores, 1800)
        return scores
    
    def _collaborative_filtering_recommendation(self, user_id, queryset):
        """
        协同过滤推荐（优化版：使用缓存和批量查询）
        使用用户-用户相似度（User-based Collaborative Filtering）
        """
        # 构建缓存键
        query_hash = str(hash(str(queryset.query)))[:10]
        cache_key = f'collab_filter_rec_{user_id}_{query_hash}'
        cached_scores = cache.get(cache_key)
        if cached_scores:
            return cached_scores
        
        scores = {}
        
        try:
            target_user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return scores
        
        # 批量获取目标用户的收藏和访问记录（优化查询）
        target_favorites = set(AttractionFavorite.objects.filter(user=target_user).values_list('attraction_id', flat=True))
        target_routes = RoutePlan.objects.filter(user_id=user_id).only('optimal_route')
        target_visited = set()
        for route in target_routes:
            if route.optimal_route and 'route' in route.optimal_route:
                target_visited.update(route.optimal_route['route'])
        
        # 如果目标用户没有足够的历史数据，返回空
        if len(target_favorites) + len(target_visited) < 2:
            return scores
        
        # 缓存用户相似度（避免重复计算）
        similarity_cache_key = f'user_similarities_{user_id}'
        user_similarities = cache.get(similarity_cache_key)
        
        if user_similarities is None:
            user_similarities = {}
            target_set = target_favorites | target_visited
            
            # 批量获取所有用户的收藏和访问记录（优化查询）
            all_user_favorites = defaultdict(set)
            for fav in AttractionFavorite.objects.select_related('user').values('user_id', 'attraction_id'):
                all_user_favorites[fav['user_id']].add(fav['attraction_id'])
            
            all_user_visited = defaultdict(set)
            all_routes = RoutePlan.objects.exclude(user_id=user_id).only('user_id', 'optimal_route')
            for route in all_routes:
                if route.optimal_route and 'route' in route.optimal_route:
                    all_user_visited[route.user_id].update(route.optimal_route['route'])
            
            # 计算用户相似度（只计算前100个最相似的用户）
            for other_user_id in all_user_favorites.keys() | all_user_visited.keys():
                if other_user_id == user_id:
                    continue
                
                other_set = all_user_favorites.get(other_user_id, set()) | all_user_visited.get(other_user_id, set())
                
                if len(target_set) == 0 or len(other_set) == 0:
                    continue
                
                intersection = len(target_set & other_set)
                union = len(target_set | other_set)
                
                if union > 0:
                    similarity = intersection / union
                    if similarity > 0:
                        user_similarities[other_user_id] = similarity
            
            # 只保留前100个最相似的用户
            user_similarities = dict(sorted(user_similarities.items(), key=lambda x: x[1], reverse=True)[:100])
            
            # 缓存相似度（1小时）
            cache.set(similarity_cache_key, user_similarities, 3600)
        
        # 批量检查相似用户的偏好（优化查询）
        similar_user_ids = list(user_similarities.keys())
        if not similar_user_ids:
            return scores
        
        # 批量获取相似用户喜欢的景点
        similar_user_favorite_attractions = defaultdict(set)
        for fav in AttractionFavorite.objects.filter(
            user_id__in=similar_user_ids,
            attraction_id__in=queryset.values_list('id', flat=True)
        ).values('user_id', 'attraction_id'):
            similar_user_favorite_attractions[fav['attraction_id']].add(fav['user_id'])
        
        # 批量获取相似用户访问过的景点
        similar_user_visited_attractions = defaultdict(set)
        similar_routes = RoutePlan.objects.filter(user_id__in=similar_user_ids).only('user_id', 'optimal_route')
        for route in similar_routes:
            if route.optimal_route and 'route' in route.optimal_route:
                for attr_id in route.optimal_route['route']:
                    if attr_id in queryset.values_list('id', flat=True):
                        similar_user_visited_attractions[attr_id].add(route.user_id)
        
        # 计算推荐分数
        attractions = queryset.only('id')
        for attraction in attractions:
            if attraction.id in target_visited:
                continue
            
            score = 0.0
            total_similarity = 0.0
            
            # 获取喜欢/访问过这个景点的相似用户
            favorite_users = similar_user_favorite_attractions.get(attraction.id, set())
            visited_users = similar_user_visited_attractions.get(attraction.id, set())
            relevant_users = favorite_users | visited_users
            
            for similar_user_id in relevant_users:
                similarity = user_similarities.get(similar_user_id, 0)
                if similarity > 0:
                    score += similarity
                    total_similarity += similarity
            
            # 归一化分数
            if total_similarity > 0:
                scores[attraction.id] = score / total_similarity
        
        # 缓存结果（30分钟）
        cache.set(cache_key, scores, 1800)
        return scores
    
    def recommend_routes(self, user_preferences=None, current_location=None, limit=5):
        """
        推荐路线
        :param user_preferences: 用户偏好
        :param current_location: 当前位置
        :param limit: 推荐数量
        """
        queryset = RoutePlan.objects.all()
        
        if user_preferences:
            budget = user_preferences.get('budget')
            categories = user_preferences.get('categories', [])
            
            if budget:
                queryset = queryset.filter(budget__lte=budget)
        
        # 按创建时间和总费用排序
        routes = queryset.order_by('-created_at')[:limit]
        
        recommendations = []
        for route in routes:
            recommendations.append({
                'route_id': route.id,
                'start_attraction': route.start_attraction.name,
                'budget': float(route.budget),
                'total_cost': float(route.total_cost),
                'total_distance': float(route.total_distance),
                'created_at': route.created_at.isoformat(),
            })
        
        return recommendations
    
    def _calculate_attraction_score(self, attraction):
        """计算景点推荐分数"""
        score = 0.0
        
        # 热度权重 40%
        score += (attraction.popularity / 100) * 0.4
        
        # 评分权重 30%
        score += (attraction.rating / 5) * 0.3
        
        # 评价数量权重 20%
        review_count = attraction.reviews.count()
        if review_count > 0:
            score += min(review_count / 50, 1) * 0.2
        
        # 价格权重 10%（价格越低分数越高）
        if attraction.ticket_price > 0:
            score += max(0, (100 - float(attraction.ticket_price)) / 100) * 0.1
        else:
            score += 0.1
        
        return min(score, 1.0)
    
    def _get_recommendation_reason(self, attraction, user_id):
        """获取推荐理由（优化版）"""
        reasons = []
        
        # 热度相关
        if attraction.popularity >= 90:
            reasons.append("超热门景点")
        elif attraction.popularity >= 80:
            reasons.append("热门景点")
        elif attraction.popularity >= 60:
            reasons.append("较受欢迎")
        
        # 评分相关
        if attraction.rating >= 4.8:
            reasons.append("超高评分")
        elif attraction.rating >= 4.5:
            reasons.append("高评分")
        elif attraction.rating >= 4.0:
            reasons.append("评分良好")
        
        # 价格相关
        if attraction.ticket_price == 0:
            reasons.append("免费开放")
        elif float(attraction.ticket_price) <= 50:
            reasons.append("价格实惠")
        
        # 评价数量相关
        review_count = attraction.reviews.count()
        if review_count >= 100:
            reasons.append("评价众多")
        elif review_count >= 50:
            reasons.append("评价较多")
        
        # 用户相关
        if user_id:
            # 检查是否基于协同过滤
            try:
                user = User.objects.get(id=user_id)
                user_favorites = AttractionFavorite.objects.filter(user=user).count()
                if user_favorites > 0:
                    reasons.append("基于您的偏好推荐")
                else:
                    reasons.append("为您精心推荐")
            except User.DoesNotExist:
                reasons.append("热门推荐")
        else:
            reasons.append("热门推荐")
        
        return "、".join(reasons) if reasons else "值得一游"


# 全局推荐引擎实例
recommendation_engine = RecommendationEngine()

